Summarize
- As a general rule, code which is explicitly version (or platform) dependent should be hidden behind a low-level macro or function
- kernel code can be optimized for a specific processor in a CPU family to get the best from the target platform
- When a module is loaded, any symbol exported by the module becomes part of the kernel symbol table
Outline
- Compiling Modules
- Loading and Unloading Modules
- Platform Dependency
- The Kernel Symbol Table
Compiling Modules
範例: Hello World Module (補充 By GPT4o)
# Makefile for building the Hello World kernel module
# 指定要編譯的目標
obj-m := hello.o
# 設定內核路徑,使用 uname -r 自動取得當前運行的內核版本
KDIR := /lib/modules/$(shell uname -r)/build
# 編譯命令
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
# 清理已生成的模組
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
說明:
- obj-m := hello.o:表示要編譯的模組檔名是 hello.c,最終生成的目標是 hello.ko
- KDIR:定義內核編譯目錄位置,這是內核模組編譯所需的依賴路徑,使用 uname -r 自動檢測當前運行的內核版本
- $(MAKE) -C $(KDIR) M=$(PWD) modules:這行指令會調用內核的構建系統來編譯模組,其中 -C $(KDIR) 切換到內核編譯目錄,而 M=$(PWD) 則表示當前模組的源代碼位於當前目錄($(PWD))
- clean:清理生成的模組文件
使用這個 Makefile 時,需要將對應的 hello.c 文件放在同一目錄下,然後可以使用 make 編譯模組,生成的模組文件為 hello.ko
The above line is not how a traditional makefile looks
- The kernel build system handles the rest
- The assignment above (which takes advantage of the extended syntax provided by GNU make) states that there is one module to be built from the object file hello.o.
- The resulting module is named hello.ko after being built from the object file
NOTE: 想更深入了解 Kernel Build 可以去載 Linux Kernel 原始碼
- kernel build system is a complex beast, and we just look at a tiny piece of it. The files
found in the Documentation/kbuild directory in the kernel source are required reading for anybody wanting to understand all that is really going on beneath the surface
Loading and Unloading Modules
insmod:
- 用來將模組載入到 Linux Kernel 中的工具
- 它的工作方式類似於 linker (ld),會將模組中的未解析符號和內核符號表中的符號連接起來
- sys_init_module 系統呼叫:
- insmod 的背後是系統呼叫 sys_init_module,它會使用 vmalloc 分配內核記憶體來存放模組代碼,然後將模組代碼拷貝到這個記憶體區域,並解析模組中的內核符號
- 最後,內核會調用模組的初始化函數來啟動模組
modprobe:
- modprobe 和 insmod 一樣可以將模組載入內核,但它會檢查模組中是否有未解析的符號,並自動加載那些定義這些符號的依賴模組
- 如果直接使用 insmod 而不加載這些依賴模組,會失敗並在系統日誌中留下 "unresolved symbols" 錯誤
rmmod:
- rmmod 用來移除已載入的模組。
- 如果模組仍然被使用(例如,某個程序仍然打開了該模組匯出的設備文件),模組無法移除
lsmod:
- 讀取 /proc/modules 來顯示已加載模組的列表,包括模組名稱、大小及使用者模組數量等
- /proc/modules 是一個虛擬文件,反映了內核當前已加載模組的狀態
- /sys/module 是另一個虛擬文件系統,提供了關於每個模組的更詳細信息,可用於調試和檢查模組參數
modinfo:
- 是一個用來顯示已編譯內核模組(.ko 文件)信息的命令。它能夠顯示模組的各種屬性和相關信息,這對於調試和模組管理非常有用
Version Dependency
The kernel does not just assume that a given module has been built against the
proper kernel version
- One of the steps in the build process is to link your module
against a file (called vermagic.o) from the current kernel tree;
- this object contains a fair amount of information about the kernel the module was built for, including the target kernel version, compiler version, and the settings of a number of important configuration variables.
When an attempt is made to load a module, this information can be tested for compatibility with the running kernel.
- If things don’t match, the module is not loaded; instead, you see something like:
# insmod hello.ko
Error inserting './hello.ko': -1 Invalid module format
- A look in the system log file (/var/log/messages or whatever your system is config
ured to use) will reveal the specific problem that caused the module to fail to load.
Most dependencies based on the kernel version can be worked around with preprocessor conditionals by exploiting KERNEL_VERSION and LINUX_VERSION_CODE
- include\linux\module.h
- UTS_RELEASE
- KERNEL_VERSION
- LINUX_VERSION_CODE
Version dependency should, however, not clutter driver code with hairy #ifdef conditionals
- the best way to deal with incompatibilities is by confining them to a specific header file
As a general rule, code which is explicitly version (or platform) dependent should be hidden behind a low-level macro or function
Platform Dependency
Unlike application developers, who must link their code with precompiled libraries
and stick to conventions on parameter passing, kernel developers can dedicate some
processor registers to specific roles, and they have done so
- Moreover, kernel code can be optimized for a specific processor in a CPU family to get the best from the target platform
vermagic.o
- When a module is loaded, the kernel checks the processor-specific configuration options for the module and makes sure they match the running kernel
- If the module was compiled with different options, it is not loaded
補充: 模組的跨環境使用問題
- 由於模組必須與內核配置及處理器架構相匹配,因此直接在一個環境下編譯的 .ko 文件通常無法在另一個不同的環境中使用,尤其是當內核配置、版本或處理器架構不同時
- 這是因為內核和模組之間的符號解析和依賴性與具體的處理器和內核配置密切相關
The Kernel Symbol Table
When a module is loaded, any symbol exported by the module becomes part of the kernel symbol table
In the usual case, a module implements its own functionality without the need to
export any symbols at all
- You need to export symbols, however, whenever other modules may benefit from using them
- New modules can use symbols exported by your module, and you can stack new
modules on top of other modules
圖的流程和模組堆疊關係:
- lp 模組 作為高層的驅動,它負責具體設備(例如打印機)與並行端口的通信,它通過 parport 來實現與並行端口的共享和協作
- parport 確保多個設備能夠在不同的時間使用同一並行端口。parport 模組 提供抽象層的端口管理服務,並將低層具體的硬體操作委託給更底層的 parport_pc
- parport_pc 是實際與硬體進行交互的驅動模組,負責管理實際的並行端口設備
- 所有這些模組都通過 Kernel API 來使用內核提供的功能和服務,例如模組的加載、資源的分配、訊息處理等
Linux kernel header 檔提供了管理符號可見性的方法,以減少命名空間污染(避免名稱衝突)並促進信息隱藏。如果模組需要向其他模組導出符號,可以使用以下 Macro:
EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);
- 這兩個 Macro 會使符號在模組外部可見,"_GPL" 版本則僅限 GPL 授權的模組使用
- 符號必須在模組文件的全局範圍內導出,因為這些 Macro 會展開為特殊用途的變量,並存儲在模組的 ELF 段中,供內核在載入時查找
補充:
- Linux Kernel 的 include/linux/init.h 定義了各種與內核初始化和模組管理相關的 section,這些 section 控制代碼和數據的生命週期,以便內核能夠在啟動過程中釋放不再需要的資源
+----------------------------------+
| |
| Executable and Linkable Format |
| |
+----------------------------------+
| .text | <--- 可執行代碼段 (執行代碼)
| |
| +------------------+ |
| | __init | | <--- 初始化函數段 (.init.text)
| +------------------+ |
| | __exit | | <--- 卸載函數段 (.exit.text)
| +------------------+ |
+----------------------------------+
| .data | <--- 已初始化數據段 (.data)
| |
| +------------------+ |
| | __initdata | | <--- 初始化數據 (.init.data)
| +------------------+ |
| | __exitdata | | <--- 卸載數據 (.exit.data)
| +------------------+ |
+----------------------------------+
| .rodata | <--- 只讀數據段 (.rodata)
| |
| +------------------+ |
| | __initconst | | <--- 初始化只讀數據 (.init.rodata)
| +------------------+ |
+----------------------------------+
| .bss | <--- 未初始化數據段 (.bss)
| |
+----------------------------------+
Reference
-
Linux驱动vermagic值